/*
    http://bhepple.freeshell.org

    Copyright (C) 2010 Bob Hepple <bhepple@freeshell.org>

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; see the file COPYING.  If not, write to
    the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
    Boston, MA 02111-1307, USA.

*/

/**
 * \file tbs.h - Safe dynamic string package. 
 * 
 * Expands strings if necessary so that most things just work without
 * fussing about 'is it long enough', 'am I passing in a NULL
 * reference' etc. 
 *
 * This library doesn't leak memory itself (according to valgrind when
 * run on Linux) so you can make a caller watertight by using
 * tbs_free() appropriately.
 *
 * In all the routines, if a (tbs *) argument is required, then
 * it means the routine may need to re-size the argument. In all
 * cases, if there is an error then the original value will _not_ be
 * modified.
 *
 * In general, tbss are not shrunk - if you have large strings
 * that you want shruken then tbs_dup them to another string and
 * free the original.
 *
 * These routines call xcalloc, xrealloc, xfree which must be provided
 * by the caller. This provides an opportunity to handle out-of-memory
 * conditions as desired. For example, you might call calloc(1, 64000)
 * on the first call to xcalloc - then, if you run out, you could free
 * the safety buffer and longjmp out to a handler to do an emergency
 * stop or other recovery. Note: xrealloc should call xmalloc if
 * handed a NULL pointer - as on GNU libc. There's a simple
 * implementation of xmalloc etc in the unit test
 * tests/tbs_test.c. Note that the usual implementation of
 * xmalloc etc is to never return a NULL pointer eg on out-of-memory.
 * tbs does not make that assumption and allows you to let
 * xmalloc() return a NULL just like malloc().
 *
 * tbss are thread safe provided the usual rules for using
 * objects in threads are applied - ie lock around write-access to
 * shared tbss. The only proviso is that if
 * tbs_set_min_maxlen is called, it should be called before
 * threads start. If called in a thread, the only confusion is that
 * threads might fight over the optimum min_maxlen to use.
 *
 * Some simple checking for data corruption can be done by setting the
 * environment variable TBSTRING_CHECK:

\verbatim
TBSTRING_CHECK unset : no checking is done
TBSTRING_CHECK=1 : checking is done and error returns made
TBSTRING_CHECK=2 : checking is done and abort(3) is called
\endverbatim
 */

#ifndef _XMALLOC_H
void *xcalloc(size_t nmemb, size_t size);
void *xmalloc(size_t size);
void xfree(void *ptr);
void *xrealloc(void *ptr, size_t size);
#endif /* _XMALLOC_H */

/**
 * A safe string object
 */
typedef struct {
    /** initial sentinel (the second is placed after the string) */
    size_t sentinel1;
    /** maximum strlen(str) */
    size_t maxlen;
    /** physical storage for string - will be expanded as needed */
    char	str[0]; 
    /** size_t sentinel2 comes next */
} *tbs;

/**
 * Create a new tbs with a specific maxlen
 * \param maxlen [IN] new maximum length
 * \return new tbs or NULL on error
 */
tbs tbs_new_maxlen(int maxlen);

/**
 * Create a new tbs from a char *
 * \param s [IN] char * initialiser
 * \return new tbs or NULL on error
 */
tbs tbs_new(const char *s);

/**
 * Free a tbs object
 * \param s [IN] object to free
 */
void tbs_free(tbs *s);

/**
 * tbs_strlen is implemented as tbs_get_length
 */
#define tbs_strlen tbs_get_length

/**
 * Return the maximum length of a tbs
 * \param s [IN] tbs
 * \return int maximum length
 */
int tbs_get_maxlen(const tbs s);

/**
 * Return the current length of a tbs
 * \param s [IN] tbs
 * \return int length
 */
int tbs_get_length(const tbs s);

/**
 * Return a pointer to the char * in a tbs - do not abuse it by
 * modifying or freeing it! (i/f to char * world)
 * \param s [IN] tbs
 * \return char * from the tbs; may be NULL (eg if s is NULL)
 */
char *tbs_get_str(tbs s);

/**
 * Add a str
 * \param dest [IN/OUT] dest tbs to be set (can be NULL) - may be enlarged
 *
 * \param str [IN] string to append
 */
tbs tbs_append_str(tbs *       dest,
                   const char *source);

/**
 * Set string on a tbs.

 * \param dest [IN/OUT] tbs to be set (can be NULL) - may be enlarged
 * \param source [IN] new value of string
 * \return new value of dest or NULL on error
 */
tbs tbs_set_str(tbs *       dest,
                char *source);

/**
 * Get sub-string from a tbs
 * \param source [IN] tbs
 * \param pos [IN] index of substring start (-1 for the last char)
 * \param num [IN] number of chars to get (can be <= 0 to get backwards)
 * \return new value of dest or NULL on error (*dest is unchanged on error)
 */
tbs tbs_get_substr(const tbs source,
                   int       pos,
                   int       num);

/**
 * Set a string in a tbs
 * \param dest [IN/OUT] tbs to be modified - may be expanded
 * \param pos [IN] position to set (zero-based, -1 means after last char)
 * \param source [IN] tbs to paste on to the dest
 * \return new value of dest or NULL on error (*dest is unchanged on error)
 */
tbs tbs_set_substr(tbs *     dest,
                   int       pos, 
                   const tbs source);

/**
 * Fill a tbs with a char
 * \param dest [IN/OUT] tbs to be modified
 * \param p [IN] value to fill
 */
void tbs_fill(tbs dest,
              int p);

/**
 * Resize a tbs - make a string bigger (doesn't shrink it)
 * \param dest [IN/OUT] tbs to modify (can be NULL and a new one will be created)
 * \param newlen [IN] new maxlen for dest
 * \return dest or NULL on error (*dest is unchanged on error)
 */
tbs tbs_resize(tbs *dest,
               int  newlen);

/**
 * Delete N chars from a tbs
 * \param dest [IN/OUT] tbs to be modified
 * \param pos [IN] position to delete (zero-based, -1 means delete last char)
 * \param len [IN] maximum number of chars to delete
 * \return new value of dest or NULL on error (*dest is unchanged on error)
 */
tbs tbs_delchars(tbs *dest,
                 int  pos,
                 int  len);

/**
 * Insert a string into a tbs
 * \param dest [IN/OUT] tbs to be modified - may be expanded
 * \param pos [IN] position to insert (zero-based, -1 inserts at the end)
 * \param source [IN] string to insert
 * \return new value of dest or NULL on error (*dest is unchanged on error)
 */
tbs tbs_insert(tbs *     dest,
               int       pos, 
               const tbs source);

/**
 * Truncate a tbs
 * \param dest [IN/OUT] tbs to be modified
 * \param pos [IN] position to truncate (zero-based) == new length
 * \return -1 on error else 0
 */
int tbs_trunc(tbs dest,
              int pos);

/**
 * Right trim space characters from tbs
 * \param dest [IN/OUT] tbs to be modified
 */
int tbs_rtrim(tbs dest);

/**
 * Left trim space characters from tbs
 * \param dest [IN/OUT] tbs to be modified
 */
int tbs_ltrim(tbs dest);

/**
 * Right & left trim space characters from tbs
 * \param dest [IN/OUT] tbs to be modified
 * \return -1 on error else 0
 */
int tbs_trim(tbs dest);

/**
 * Return the last char in a tbs
 * \param s [IN] tbs to search
 * \return last char in str or -1 on error
 */
int tbs_lastchar(const tbs s);

/**
 * Upper-case shift a tbs
 * \param dest [IN] tbs to be shifted
 */
void tbs_upper(tbs dest);

/**
 * Lower-case shift a tbs
 * \param dest [IN] tbs to be shifted
 */
void tbs_lower(tbs dest);

/**
 * Get memory required to store a tbs of a particular size
 * \param maxlen [IN] size of string
 * \return returns physical size of tbs in bytes
 */
size_t sizeof_tbs(size_t maxlen);

/**
 * Set the minimum maxlen (initially 1) for tbss created from now on.
 * \param maxlen [IN] > 0
 */
void tbs_set_min_maxlen(int maxlen);

/**
 * Compatibility functions:
 */

/** 
 * Compare two tbss
 * \param s1 [IN] first tbs to be compared
 * \param s2 [IN] second tbs to be compared
 * \return <0, 0 or >0 as s1 is < == or > s2
 */
int tbs_strcmp(const tbs s1, const tbs s2);

/** 
 * Compare up to n characters of two tbss
 * \param s1 [IN] first tbs to be compared
 * \param s2 [IN] second tbs to be compared
 * \param n [IN] maximum number of characters to compare
 * \return <0, 0 or >0 as s1 is < == or > s2
 */
int tbs_strncmp(const tbs s1, const tbs s2, size_t n);

/** 
 * Compare two tbss ignoring case
 * \param s1 [IN] first tbs to be compared
 * \param s2 [IN] second tbs to be compared
 * \return <0, 0 or >0 as s1 is < == or > s2
 */
int tbs_strcasecmp(const tbs s1, const tbs s2);

/** 
 * Compare up to n characters of two tbss ignoring case
 * \param s1 [IN] first tbs to be compared
 * \param s2 [IN] second tbs to be compared
 * \param n [IN] maximum number of characters to compare
 * \return <0, 0 or >0 as s1 is < == or > s2
 */
int tbs_strncasecmp(const tbs s1, const tbs s2, size_t n);

/** 
 * Copy contents of a tbs to an existing one
 * \param dest [IN/OUT] tbs to be modified - may be expanded
 * \param source [IN] tbs to be appended to dest
 * \return new value of dest or NULL on error (*dest is unchanged on error)
 */
tbs tbs_strcpy(tbs *dest, const tbs source);

/** 
 * Append a tbs onto an existing one. It's safe to append a string to itself.
 * \param dest [IN/OUT] tbs to be modified - may be expanded
 * \param source [IN] tbs to be appended to dest
 * \return new value of dest or NULL on error (*dest is unchanged on error)
 */
tbs tbs_strcat(tbs *dest, const tbs source);

/**
 * Find a char in a tbs
 * \param s [IN] tbs to search
 * \param c [IN] char to search
 * \return position of first match in tbs or -1 if none
 */
int tbs_strchr(const tbs s, int c);

/**
 * Find a char in a tbs
 * \param s [IN] tbs to search
 * \param c [IN] char to search
 * \return position of last match in tbs or -1 if none
 */
int tbs_strrchr(const tbs s, int c);

/**
 * Create a new tbs as a copy of an existing one
 * \param source [IN] source tbs 
 * \return new tbs copied from source
 */
tbs tbs_strdup(const tbs source);

/**
 * search a tbs for a set of characters
 * \param s [IN] tbs to search
 * \param accept [IN] tbs of search letters
 * \return the number of characters in the initial segment of s which consist only of characters from accept.
 */
size_t tbs_strspn(const tbs s, const tbs accept);

/**
 * search a tbs for a set of characters
 * \param s [IN] tbs to search
 * \param accept [IN] tbs of search letters
 * \return the number of characters in the initial segment of s which consist only of characters which are not in reject.
 */
size_t tbs_strcspn(const tbs s, const tbs accept);

/**
 * extract tokens from strings - not quite the same as strtok/strsep
 * \param s [IN] tbs to search
 * \param pos [IN/OUT] pointer to an integer representing the position in s to start the search - will be modified to point after the first such token
 * \param delim [IN] tbs of search letters
 * \return a new tbs containing the next token, or NULL if there are no more tokens
 */
tbs tbs_strtok(tbs s, int *pos, const tbs delim);

/**
 * Find a string in a tbs
 * \param s [IN] tbs to search
 * \param substr [IN] substring to search
 * \return position of first match in tbs or -1 if not found
 */
int tbs_strstr(const tbs s, const tbs substr);

/**
 * sprintf to a tbs
 * \param dest [IN/OUT] receiving string (may NOT be NULL!)
 * \param format [IN] printf format string
 * \param ... [IN] args to print
 * \return number of characters put into dest; negative number on error (*dest is unchanged on error)
 */
int tbs_sprintf(tbs *dest, char *format, ...);
